Load libraries


#### Load packages ####
library(camprotR)
library(ggplot2)
library(tidyverse)
library(MSnbase)
library(biobroom)

Read in the PSM data

psm_res <- readRDS('../results/psm_res.rds')
psm_res %>% names() %>% lapply(function(x){
  psm_res[[x]] %>%
    fData() %>%
    mutate(method=x)
}) %>%
  do.call(what='rbind') %>%
  group_by(method) %>%
  summarise(median_sn=10^median(log10(Average.Reporter.SN), na.rm=TRUE))
`summarise()` ungrouping output (override with `.groups` argument)
129.15/42
[1] 3.075
p <- psm_res %>% names() %>% lapply(function(x){
  psm_res[[x]] %>%
    fData() %>%
    mutate(method=x)
}) %>%
  do.call(what='rbind') %>%
  ggplot(aes(Average.Reporter.SN, colour=method)) +
    geom_density() +
    theme_camprot()

print(p)

print(p + scale_x_log10())


print(p + aes(Isolation.Interference.in.Percent))

Plotting the proportion of missing values


psm_res %>% names() %>% lapply(function(x){
  
  all <- psm_res[[x]]
  hs <- all[fData(all)$species=='H.sapiens']
  sc <- all[fData(all)$species=='S.cerevisiae']
  
  slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
  for(slice in names(slices)){
    p <- slices[[slice]] %>% plot_missing_SN() +
      ggtitle(sprintf('%s - %s', x, slice))
    print(p)
  
    p <- slices[[slice]] %>% plot_missing_SN_per_sample() +
      ggtitle(sprintf('%s - %s', x, slice))
    print(p)
  }
  return(NULL)
})
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
[[1]]
NULL

[[2]]
NULL

OK, so essentially all the missing values are restricted to PSMs with low (<20) Signal:Noise ratios.


source('./R/get_quant_vs_mean.R')


quant_vs_mean <- psm_res %>% lapply(get_quant_vs_mean) 

quant_vs_mean <- quant_vs_mean %>% lapply(function(x){
  x %>%
    mutate(binned_interference=Hmisc::cut2(
      Isolation.Interference.in.Percent, cuts=c(0,1,5,10,seq(20,100,20))),
      binned_q=Hmisc::cut2(Percolator.q.Value, cuts=c(0, 0.001, 0.002, 0.005, 0.01)),
      binned_average_sn=Hmisc::cut2(Average.Reporter.SN, cuts=c(0,10,20,30,40,60,100)),
      binned_intensity=Hmisc::cut2(intensity, cuts=c(0,10,20,30,40,60,100)))
})
quant_vs_mean %>% lapply(dim)
$`AGC: 2E5`
[1] 1980560      23

$`AGC: 5E4`
[1] 2166658      23
exp_design <- pData(psm_res$`AGC: 2E5`) %>%
  select(condition, S.cerevisiae=yeast, H.sapiens=human) %>%
  unique()
  
sc_spikes <- exp_design$S.cerevisiae
hs_spikes <- exp_design$H.sapiens

get_ground_truth <- function(sc_spikes, hs_spikes, ix_1, ix_2){
  comparison <- sprintf('%s vs %s', sc_spikes[ix_2], sc_spikes[ix_1])
  hs_ground_truth <- hs_spikes[ix_2]/hs_spikes[ix_1]
  sc_ground_truth <- sc_spikes[ix_2]/sc_spikes[ix_1]
  return(c(comparison, hs_ground_truth, sc_ground_truth))
}
library(gtools)

expected <- apply(permutations(n=3,r=2), 1, function(x){
  get_ground_truth(sc_spikes, hs_spikes, x[1], x[2])
}) %>% t() %>% data.frame() %>%
  setNames(c('comparison', 'H.sapiens', 'S.cerevisiae')) %>%
  mutate_at(vars(S.cerevisiae, 
                 H.sapiens), 
            funs(as.numeric)) %>%
  pivot_longer(-comparison, names_to='species', values_to='expected')
`funs()` is deprecated as of dplyr 0.8.0.
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.
print(expected)

positive_comparisons <- expected %>% filter(species=='S.cerevisiae', expected>1) %>%
  pull(comparison)
quant_vs_mean %>% names() %>% lapply(function(x){
  for(sp in c("S.cerevisiae", "H.sapiens")){
    p <- quant_vs_mean[[x]] %>%
      filter(species==sp, comparison %in% positive_comparisons) %>%
      ggplot(aes(binned_interference, diff, colour=below_notch)) +
      geom_violin() +
      theme_camprot(base_size=10) +
      theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
      facet_wrap(~comparison, scales='free') +
      geom_hline(aes(yintercept=log2(expected)),
                 data=expected[(expected$species==sp &
                                  expected$comparison %in% positive_comparisons),],
                 colour='grey', linetype=2) +
      xlab('Interference') +
      ylab('Difference in intensity') +
      theme(aspect.ratio=.3) +
      ggtitle(x)
    
    print(p)
  }
  
  p <- quant_vs_mean[[x]] %>%
    filter(species=='S.cerevisiae', Isolation.Interference.in.Percent<=60,
           comparison %in% positive_comparisons) %>%
    ggplot(aes(diff, colour=binned_interference)) +
    geom_density() +
    theme_camprot(base_size=10) +
    theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
    facet_grid(below_notch~comparison, scales='free') +
    geom_vline(aes(xintercept=log2(expected)),
               data=expected[(expected$species=='S.cerevisiae' &
                                expected$comparison %in% positive_comparisons),],
               colour='grey', linetype=2) +
    xlab('Difference in intensity') +
    ylab('Density') +
    xlim(-6,3) +
    theme(aspect.ratio=2) +
    ggtitle(x)
  
  print(p)
})
[[1]]

[[2]]

quant_vs_mean %>% names() %>% lapply(function(x){
  p <- quant_vs_mean[[x]] %>%
    filter(Isolation.Interference.in.Percent<=50, # no need to consider interference>=50%
           comparison %in% positive_comparisons) %>% 
    filter(species=='S.cerevisiae', !below_notch) %>%
    ggplot(aes(diff, colour=binned_intensity)) +
    geom_density() +
    theme_camprot(base_size=15) +
    theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
    facet_grid(binned_interference~comparison, scales='free') +
    geom_vline(aes(xintercept=log2(expected)),
               data=expected[(expected$species=='S.cerevisiae' &
                                expected$comparison %in% positive_comparisons),],
               colour=get_cat_palette(1), linetype=2) +
    ylab('Density') +
    xlab('Difference in intensity') +
    xlim(-6,3) +
    ggtitle(x) +
    scale_colour_discrete(name='Intensity')
  
  print(p)
  print(p + aes(colour=binned_interference) + facet_grid(binned_average_sn~comparison) +
    scale_colour_discrete(name='Interference (%)'))
  
  print(p + aes(colour=binned_q) + scale_colour_discrete(name='Percolator Q'))
  print(p + aes(colour=binned_interference) + facet_grid(binned_q~comparison) +
          scale_colour_discrete(name='Interference (%)'))

  
  return(NULL)
})
[[1]]
NULL

[[2]]
NULL

quant_vs_mean %>% names() %>% lapply(function(x){
    p <- quant_vs_mean[[x]] %>%
    filter(Isolation.Interference.in.Percent<=60) %>% # no need to consider interference>=60%
    filter(species=='S.cerevisiae', !below_notch, comparison=='6 vs 1') %>%
    group_by(binned_interference, binned_intensity) %>%
    summarise(median_diff=2^median(diff, na.rm=TRUE), n=length(diff)) %>%
    ggplot(aes(binned_interference, binned_intensity, fill=median_diff)) +
    geom_tile(colour='grey') +
    theme_camprot(base_size=15) +
    scale_fill_gradient(high=get_cat_palette(2)[2],
                        low='white',
                        limits=c(0, 6), name='Observed\nfold change') +
    theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
    xlab('Binned interference') +
    ylab('Binned intensity') +
    ggtitle(x)
    
    print(p + geom_text(aes(label=round(median_diff, 1)), size=3))
    
    print(p +
            aes(fill=n) +
            scale_fill_gradient(high=get_cat_palette(3)[3],
                                low='white') +
            geom_text(aes(label=n), size=3) )
})
`summarise()` regrouping output by 'binned_interference' (override with `.groups` argument)
[[1]]

[[2]]

quant_vs_mean %>% names() %>% lapply(function(x){
    quant_vs_mean[[x]] %>%
    filter(Isolation.Interference.in.Percent<=60) %>% # no need to consider interference>=60%
    filter(species=='S.cerevisiae', !below_notch, comparison=='6 vs 1') %>%
    group_by(binned_q, binned_interference, binned_intensity) %>%
    summarise(median_diff=2^median(diff, na.rm=TRUE)) %>%
    ggplot(aes(binned_interference, binned_intensity, fill=median_diff)) +
    geom_tile(colour='grey') +
    facet_wrap(~binned_q) +
    theme_camprot(base_size=15) +
    scale_fill_gradient2(high=get_cat_palette(2)[2],
                         low=get_cat_palette(2)[1],
                         mid='white', midpoint=0,
                        limits=c(-1, 6), name='Observed\nfold change') +
    theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1),
          panel.background=element_rect(fill="grey")) +
    xlab('Binned interference') +
    ylab('Binned Intensity') +
    ggtitle(x)
})
`summarise()` regrouping output by 'binned_q', 'binned_interference' (override with `.groups` argument)
`summarise()` regrouping output by 'binned_q', 'binned_interference' (override with `.groups` argument)
[[1]]

[[2]]


quant_vs_mean %>% names() %>% lapply(function(x){
  p <- quant_vs_mean[[x]] %>%
    select(id, species, binned_q, binned_average_sn, binned_interference) %>%
    unique() %>%
    group_by(species, binned_q, binned_average_sn, binned_interference) %>%
    tally() %>%
    ggplot(aes(binned_interference, n)) +
    geom_bar(stat='identity') +
    facet_wrap(~species, scales='free') +
    theme_camprot(base_size=15) +
    theme(axis.text.x=element_text(angle=45, vjust=1, hjust=1)) +
    ggtitle(x)
  
  print(p)
  print(p + aes(binned_q))
  print(p + aes(binned_average_sn) +
          xlab('Signal/Noise'))
  
  return(NULL)
})
[[1]]
NULL

[[2]]
NULL

Based on the above, I’m going to use the following thresholds:

For now, we won’t filer using SN since we want to explore the impact of the notch on fold changes in more detail first

First though, let’s filter by Percolator Q value or Isolation interence alone and check how isolation interference affects PSM-level fold change estimates.

quant_vs_mean %>% names() %>% lapply(function(x){
  
  to_plot_q_flt <- quant_vs_mean[[x]] %>%
    filter(Percolator.q.Value<=0.005) %>%
    filter(species=='S.cerevisiae') %>%
    filter(Isolation.Interference.in.Percent<50) %>%
    arrange(Isolation.Interference.in.Percent)
  
  to_plot_interference_flt <- quant_vs_mean[[x]] %>%
    filter(Isolation.Interference.in.Percent<=10) %>%
    filter(species=='S.cerevisiae') %>%
    arrange(Percolator.q.Value)
  
  p <- to_plot_q_flt %>%
    ggplot(aes(log2(intensity), diff)) +
    geom_point(size=0.1, alpha=0.1, colour='grey80') +
    theme_camprot(base_size=12) +
    facet_wrap(~comparison, scales='free_y') +
    geom_hline(aes(yintercept=log2(expected)),
               data=expected[expected$species=='S.cerevisiae',],
               colour='black', linetype=2) +
    xlab('Tag intensity (log2)') +
    ylab('Difference in intensity (log2)') +
    scale_colour_manual(values=c(get_cat_palette(7)),
                        name='Isolation interference (%)') +
    ggtitle(x)
  
  print(p)
  print(p + geom_smooth(aes(colour=binned_interference), se=FALSE, size=0.5))
  print(p %+% to_plot_interference_flt +
          geom_smooth(aes(colour=binned_q), se=FALSE, size=0.5) +
          scale_colour_manual(values=c(get_cat_palette(7)),
                        name='Percolator Q value'))

  return(NULL)
})
[[1]]
NULL

[[2]]
NULL

Let’s plot the intensity vs difference in intensity for all comparisons and filtering schemes.

quant_vs_mean %>% names() %>% lapply(function(x){
  
    tmp_data <-  quant_vs_mean[[x]] %>%
      filter(species!='mixed', !comparison %in% positive_comparisons)
    
    p <- tmp_data %>%
      ggplot(aes(log2(intensity), diff)) +
      geom_point(size=0.1, alpha=0.05, colour='grey10') +
      geom_density_2d(size=0.25, colour=get_cat_palette(1)) +
      theme_camprot(base_size=12) +
      facet_grid(species~comparison, scales='free_y') +
      geom_hline(aes(yintercept=log2(expected)),
                 data=expected[!expected$comparison %in% positive_comparisons,],
                 colour='black', linetype=2) +
      xlab('Tag intensity (log2)') +
      ylab('Difference in intensity (log2)') +
      ggtitle(x) +
      coord_cartesian(ylim=c(-4,4))
  
    print(p)

    print(p %+% (tmp_data %>%
                   filter(Percolator.q.Value<=0.005)))
    print(p %+% (tmp_data %>%
                   filter(Isolation.Interference.in.Percent<=10)))
    print(p %+% (tmp_data %>%
                   filter(Percolator.q.Value<=0.005, Isolation.Interference.in.Percent<=10)))
    print(p %+% (tmp_data %>%
                   filter(Percolator.q.Value<=0.005, Isolation.Interference.in.Percent<=10, Average.Reporter.SN>10)))
    return(NULL)
})
[[1]]
NULL

[[2]]
NULL

Now, let’s filter the PSMs against these thresholds.

psm_res_flt <- psm_res %>% lapply(function(x){
  out <- filter_TMT_PSMs(x, inter_thresh=20, sn_thresh=0)
  
  out <- out[fData(out)$Percolator.q.Value<=0.001,]
  camprotR:::message_parse(fData(out),
                           'Master.Protein.Accessions',
                           "Percolator Q value filtering")
  out
})
Filtering PSMs...
99650 features found from 10512 master proteins => No quant filtering
72364 features found from 9674 master proteins => Co-isolation filtering
72364 features found from 9674 master proteins => S:N ratio filtering
56814 features found from 8696 master proteins => Percolator Q value filtering
Filtering PSMs...
109197 features found from 10974 master proteins => No quant filtering
77629 features found from 10061 master proteins => Co-isolation filtering
77629 features found from 10061 master proteins => S:N ratio filtering
62610 features found from 9164 master proteins => Percolator Q value filtering
psm_res_flt_sn <- psm_res_flt %>% lapply(function(x){
  out <- filter_TMT_PSMs(x, inter_thresh=20, sn_thresh=10)
  out
})
Filtering PSMs...
56814 features found from 8696 master proteins => No quant filtering
56814 features found from 8696 master proteins => Co-isolation filtering
55292 features found from 8619 master proteins => S:N ratio filtering
Filtering PSMs...
62610 features found from 9164 master proteins => No quant filtering
62610 features found from 9164 master proteins => Co-isolation filtering
57787 features found from 8965 master proteins => S:N ratio filtering

psm_res %>% names() %>% lapply(function(x){
  
  datasets <- list('unfiltered'=psm_res, 'filtered'=psm_res_flt, '\nfiltered, inc S/N'=psm_res_flt_sn)
  
  for(dataset in names(datasets)){
    
    all <- datasets[[dataset]][[x]]
    hs <- all[fData(all)$species=='H.sapiens']
    sc <- all[fData(all)$species=='S.cerevisiae']
    
    slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
    for(slice in names(slices)){
      p <- slices[[slice]] %>% plot_TMT_notch() +
        ggtitle(sprintf('%s - %s - %s', x, slice, dataset))
      print(p)
    }
  }
  
  return(NULL)
  
})
[[1]]
NULL

[[2]]
NULL

Per-tag notch plots


psm_res %>% names() %>% lapply(function(x){
  
  datasets <- list('unfiltered'=psm_res, 'filtered'=psm_res_flt, '\nfiltered, inc S/N'=psm_res_flt_sn)
  
  for(dataset in names(datasets)){
    
    all <- datasets[[dataset]][[x]]
    hs <- all[fData(all)$species=='H.sapiens']
    sc <- all[fData(all)$species=='S.cerevisiae']
    
    slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
    for(slice in names(slices)){
      p <- slices[[slice]] %>% plot_TMT_notch(facet_by_sample=TRUE) +
        ggtitle(sprintf('%s - %s - %s', x, slice, dataset))
      print(p)
    }
  }
  
  return(NULL)
  
})
[[1]]
NULL

[[2]]
NULL

Tallies for fraction sub-notch PSMs per protein


psm_res %>% names() %>% lapply(function(x){
  
  datasets <- list('unfiltered'=psm_res, 'filtered'=psm_res_flt, '\nfiltered, inc S/N'=psm_res_flt_sn)
  
  for(dataset in names(datasets)){
    
    all <- datasets[[dataset]][[x]]
    hs <- all[fData(all)$species=='H.sapiens']
    sc <- all[fData(all)$species=='S.cerevisiae']
    
    slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
    for(slice in names(slices)){
      
      notch_per_protein <- get_notch_per_protein(slices[[slice]]) %>%
        filter(fraction_below>0)
      
      p <- plot_fraction_below_notch_per_prot(notch_per_protein) +
        ggtitle(sprintf('%s - %s - %s', x, slice, dataset))
      
      print(p)
      }
  }
  
  return(NULL)
  
})
[[1]]
NULL

[[2]]
NULL

Missing values frequencies.

psm_res %>% names() %>% lapply(function(x){
  
  datasets <- list('unfiltered'=psm_res, 'filtered'=psm_res_flt, '\nfiltered, inc S/N'=psm_res_flt_sn)
  
  for(dataset in names(datasets)){
    
    all <- datasets[[dataset]][[x]]
    hs <- all[fData(all)$species=='H.sapiens']
    sc <- all[fData(all)$species=='S.cerevisiae']
    
    slices <- list('All'=all, 'H.sapiens'=hs, 'S.cerevisiae'=sc)
    for(slice in names(slices)){
      plotNA(slices[[slice]], pNA = 0)
    }
  }
  
  return(NULL)
  
})
[[1]]
NULL

[[2]]
NULL

Save out objects for downstream notebooks

saveRDS(quant_vs_mean, '../results/quant_vs_mean.rds')
saveRDS(psm_res_flt, '../results/psm_res_flt.rds')
saveRDS(psm_res_flt_sn, '../results/psm_res_flt_sn.rds')
saveRDS(expected, '../results/expected.rds')
LS0tCnRpdGxlOiAnRmlsdGVyIFBTTXMnCmF1dGhvcjoKICAtIG5hbWU6ICJUb20gU21pdGgiCiAgICBhZmZpbGlhdGlvbjogIkNhbWJyaWRnZSBDZW50cmUgZm9yIFByb3Rlb21pY3MiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCIKYWJzdHJhY3Q6IHwgCiAgSGVyZSwgd2UgZmlsdGVyIHRoZSBQU00tbGV2ZWwgUEQgb3V0cHV0LCB3aXRoIHRocmVzaG9sZHMgaW5mb3JtZWQgYnkgbWlzc2luZyB2YWx1ZXMsCiAgbm90Y2ggcHJvbWluZW5jZSBhbmQgb2JzZXJ2ZWQgZm9sZCBjaGFuZ2VzIHZzIGdyb3VuZCB0cnV0aHMuCm91dHB1dDoKICBwZGZfZG9jdW1lbnQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdApnZW9tZXRyeTogbWFyZ2luPTFpbgpmb250ZmFtaWx5OiBtYXRocGF6bwpmb250c2l6ZTogMTFwdAotLS0KCkxvYWQgbGlicmFyaWVzCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFfQoKIyMjIyBMb2FkIHBhY2thZ2VzICMjIyMKbGlicmFyeShjYW1wcm90UikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShNU25iYXNlKQpsaWJyYXJ5KGJpb2Jyb29tKQpgYGAKClJlYWQgaW4gdGhlIFBTTSBkYXRhCmBgYHtyfQpwc21fcmVzIDwtIHJlYWRSRFMoJy4uL3Jlc3VsdHMvcHNtX3Jlcy5yZHMnKQpgYGAKCmBgYHtyfQpwc21fcmVzICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgcHNtX3Jlc1tbeF1dICU+JQogICAgZkRhdGEoKSAlPiUKICAgIG11dGF0ZShtZXRob2Q9eCkKfSkgJT4lCiAgZG8uY2FsbCh3aGF0PSdyYmluZCcpICU+JQogIGdyb3VwX2J5KG1ldGhvZCkgJT4lCiAgc3VtbWFyaXNlKG1lZGlhbl9zbj0xMF5tZWRpYW4obG9nMTAoQXZlcmFnZS5SZXBvcnRlci5TTiksIG5hLnJtPVRSVUUpKQoxMjkuMTUvNDIKYGBgCgpgYGB7cn0KcCA8LSBwc21fcmVzICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgcHNtX3Jlc1tbeF1dICU+JQogICAgZkRhdGEoKSAlPiUKICAgIG11dGF0ZShtZXRob2Q9eCkKfSkgJT4lCiAgZG8uY2FsbCh3aGF0PSdyYmluZCcpICU+JQogIGdncGxvdChhZXMoQXZlcmFnZS5SZXBvcnRlci5TTiwgY29sb3VyPW1ldGhvZCkpICsKICAgIGdlb21fZGVuc2l0eSgpICsKICAgIHRoZW1lX2NhbXByb3QoKQoKcHJpbnQocCkKcHJpbnQocCArIHNjYWxlX3hfbG9nMTAoKSkKCnByaW50KHAgKyBhZXMoSXNvbGF0aW9uLkludGVyZmVyZW5jZS5pbi5QZXJjZW50KSkKYGBgCgpQbG90dGluZyB0aGUgcHJvcG9ydGlvbiBvZiBtaXNzaW5nIHZhbHVlcyAKYGBge3J9Cgpwc21fcmVzICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgCiAgYWxsIDwtIHBzbV9yZXNbW3hdXQogIGhzIDwtIGFsbFtmRGF0YShhbGwpJHNwZWNpZXM9PSdILnNhcGllbnMnXQogIHNjIDwtIGFsbFtmRGF0YShhbGwpJHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnXQogIAogIHNsaWNlcyA8LSBsaXN0KCdBbGwnPWFsbCwgJ0guc2FwaWVucyc9aHMsICdTLmNlcmV2aXNpYWUnPXNjKQogIGZvcihzbGljZSBpbiBuYW1lcyhzbGljZXMpKXsKICAgIHAgPC0gc2xpY2VzW1tzbGljZV1dICU+JSBwbG90X21pc3NpbmdfU04oKSArCiAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcycsIHgsIHNsaWNlKSkKICAgIHByaW50KHApCiAgCiAgICBwIDwtIHNsaWNlc1tbc2xpY2VdXSAlPiUgcGxvdF9taXNzaW5nX1NOX3Blcl9zYW1wbGUoKSArCiAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcycsIHgsIHNsaWNlKSkKICAgIHByaW50KHApCiAgfQogIHJldHVybihOVUxMKQp9KQpgYGAKT0ssIHNvIGVzc2VudGlhbGx5IGFsbCB0aGUgbWlzc2luZyB2YWx1ZXMgYXJlIHJlc3RyaWN0ZWQgdG8gUFNNcyB3aXRoIGxvdyAoPDIwKSBTaWduYWw6Tm9pc2UgcmF0aW9zLgoKCmBgYHtyfQoKc291cmNlKCcuL1IvZ2V0X3F1YW50X3ZzX21lYW4uUicpCgoKcXVhbnRfdnNfbWVhbiA8LSBwc21fcmVzICU+JSBsYXBwbHkoZ2V0X3F1YW50X3ZzX21lYW4pIAoKcXVhbnRfdnNfbWVhbiA8LSBxdWFudF92c19tZWFuICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgeCAlPiUKICAgIG11dGF0ZShiaW5uZWRfaW50ZXJmZXJlbmNlPUhtaXNjOjpjdXQyKAogICAgICBJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQsIGN1dHM9YygwLDEsNSwxMCxzZXEoMjAsMTAwLDIwKSkpLAogICAgICBiaW5uZWRfcT1IbWlzYzo6Y3V0MihQZXJjb2xhdG9yLnEuVmFsdWUsIGN1dHM9YygwLCAwLjAwMSwgMC4wMDIsIDAuMDA1LCAwLjAxKSksCiAgICAgIGJpbm5lZF9hdmVyYWdlX3NuPUhtaXNjOjpjdXQyKEF2ZXJhZ2UuUmVwb3J0ZXIuU04sIGN1dHM9YygwLDEwLDIwLDMwLDQwLDYwLDEwMCkpLAogICAgICBiaW5uZWRfaW50ZW5zaXR5PUhtaXNjOjpjdXQyKGludGVuc2l0eSwgY3V0cz1jKDAsMTAsMjAsMzAsNDAsNjAsMTAwKSkpCn0pCmBgYAoKCmBgYHtyfQpxdWFudF92c19tZWFuICU+JSBsYXBwbHkoZGltKQpgYGAKCgoKYGBge3J9CmV4cF9kZXNpZ24gPC0gcERhdGEocHNtX3JlcyRgQUdDOiAyRTVgKSAlPiUKICBzZWxlY3QoY29uZGl0aW9uLCBTLmNlcmV2aXNpYWU9eWVhc3QsIEguc2FwaWVucz1odW1hbikgJT4lCiAgdW5pcXVlKCkKICAKc2Nfc3Bpa2VzIDwtIGV4cF9kZXNpZ24kUy5jZXJldmlzaWFlCmhzX3NwaWtlcyA8LSBleHBfZGVzaWduJEguc2FwaWVucwoKZ2V0X2dyb3VuZF90cnV0aCA8LSBmdW5jdGlvbihzY19zcGlrZXMsIGhzX3NwaWtlcywgaXhfMSwgaXhfMil7CiAgY29tcGFyaXNvbiA8LSBzcHJpbnRmKCclcyB2cyAlcycsIHNjX3NwaWtlc1tpeF8yXSwgc2Nfc3Bpa2VzW2l4XzFdKQogIGhzX2dyb3VuZF90cnV0aCA8LSBoc19zcGlrZXNbaXhfMl0vaHNfc3Bpa2VzW2l4XzFdCiAgc2NfZ3JvdW5kX3RydXRoIDwtIHNjX3NwaWtlc1tpeF8yXS9zY19zcGlrZXNbaXhfMV0KICByZXR1cm4oYyhjb21wYXJpc29uLCBoc19ncm91bmRfdHJ1dGgsIHNjX2dyb3VuZF90cnV0aCkpCn0KbGlicmFyeShndG9vbHMpCgpleHBlY3RlZCA8LSBhcHBseShwZXJtdXRhdGlvbnMobj0zLHI9MiksIDEsIGZ1bmN0aW9uKHgpewogIGdldF9ncm91bmRfdHJ1dGgoc2Nfc3Bpa2VzLCBoc19zcGlrZXMsIHhbMV0sIHhbMl0pCn0pICU+JSB0KCkgJT4lIGRhdGEuZnJhbWUoKSAlPiUKICBzZXROYW1lcyhjKCdjb21wYXJpc29uJywgJ0guc2FwaWVucycsICdTLmNlcmV2aXNpYWUnKSkgJT4lCiAgbXV0YXRlX2F0KHZhcnMoUy5jZXJldmlzaWFlLCAKICAgICAgICAgICAgICAgICBILnNhcGllbnMpLCAKICAgICAgICAgICAgZnVucyhhcy5udW1lcmljKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKC1jb21wYXJpc29uLCBuYW1lc190bz0nc3BlY2llcycsIHZhbHVlc190bz0nZXhwZWN0ZWQnKQoKcHJpbnQoZXhwZWN0ZWQpCgpwb3NpdGl2ZV9jb21wYXJpc29ucyA8LSBleHBlY3RlZCAlPiUgZmlsdGVyKHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnLCBleHBlY3RlZD4xKSAlPiUKICBwdWxsKGNvbXBhcmlzb24pCmBgYApgYGB7cn0KcXVhbnRfdnNfbWVhbiAlPiUgbmFtZXMoKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIGZvcihzcCBpbiBjKCJTLmNlcmV2aXNpYWUiLCAiSC5zYXBpZW5zIikpewogICAgcCA8LSBxdWFudF92c19tZWFuW1t4XV0gJT4lCiAgICAgIGZpbHRlcihzcGVjaWVzPT1zcCwgY29tcGFyaXNvbiAlaW4lIHBvc2l0aXZlX2NvbXBhcmlzb25zKSAlPiUKICAgICAgZ2dwbG90KGFlcyhiaW5uZWRfaW50ZXJmZXJlbmNlLCBkaWZmLCBjb2xvdXI9YmVsb3dfbm90Y2gpKSArCiAgICAgIGdlb21fdmlvbGluKCkgKwogICAgICB0aGVtZV9jYW1wcm90KGJhc2Vfc2l6ZT0xMCkgKwogICAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTEsIGhqdXN0PTEpKSArCiAgICAgIGZhY2V0X3dyYXAofmNvbXBhcmlzb24sIHNjYWxlcz0nZnJlZScpICsKICAgICAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdD1sb2cyKGV4cGVjdGVkKSksCiAgICAgICAgICAgICAgICAgZGF0YT1leHBlY3RlZFsoZXhwZWN0ZWQkc3BlY2llcz09c3AgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXhwZWN0ZWQkY29tcGFyaXNvbiAlaW4lIHBvc2l0aXZlX2NvbXBhcmlzb25zKSxdLAogICAgICAgICAgICAgICAgIGNvbG91cj0nZ3JleScsIGxpbmV0eXBlPTIpICsKICAgICAgeGxhYignSW50ZXJmZXJlbmNlJykgKwogICAgICB5bGFiKCdEaWZmZXJlbmNlIGluIGludGVuc2l0eScpICsKICAgICAgdGhlbWUoYXNwZWN0LnJhdGlvPS4zKSArCiAgICAgIGdndGl0bGUoeCkKICAgIAogICAgcHJpbnQocCkKICB9CiAgCiAgcCA8LSBxdWFudF92c19tZWFuW1t4XV0gJT4lCiAgICBmaWx0ZXIoc3BlY2llcz09J1MuY2VyZXZpc2lhZScsIElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudDw9NjAsCiAgICAgICAgICAgY29tcGFyaXNvbiAlaW4lIHBvc2l0aXZlX2NvbXBhcmlzb25zKSAlPiUKICAgIGdncGxvdChhZXMoZGlmZiwgY29sb3VyPWJpbm5lZF9pbnRlcmZlcmVuY2UpKSArCiAgICBnZW9tX2RlbnNpdHkoKSArCiAgICB0aGVtZV9jYW1wcm90KGJhc2Vfc2l6ZT0xMCkgKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCB2anVzdD0xLCBoanVzdD0xKSkgKwogICAgZmFjZXRfZ3JpZChiZWxvd19ub3RjaH5jb21wYXJpc29uLCBzY2FsZXM9J2ZyZWUnKSArCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PWxvZzIoZXhwZWN0ZWQpKSwKICAgICAgICAgICAgICAgZGF0YT1leHBlY3RlZFsoZXhwZWN0ZWQkc3BlY2llcz09J1MuY2VyZXZpc2lhZScgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cGVjdGVkJGNvbXBhcmlzb24gJWluJSBwb3NpdGl2ZV9jb21wYXJpc29ucyksXSwKICAgICAgICAgICAgICAgY29sb3VyPSdncmV5JywgbGluZXR5cGU9MikgKwogICAgeGxhYignRGlmZmVyZW5jZSBpbiBpbnRlbnNpdHknKSArCiAgICB5bGFiKCdEZW5zaXR5JykgKwogICAgeGxpbSgtNiwzKSArCiAgICB0aGVtZShhc3BlY3QucmF0aW89MikgKwogICAgZ2d0aXRsZSh4KQogIAogIHByaW50KHApCn0pCmBgYApgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCwgd2FybmluZz1GQUxTRX0KcXVhbnRfdnNfbWVhbiAlPiUgbmFtZXMoKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIHAgPC0gcXVhbnRfdnNfbWVhbltbeF1dICU+JQogICAgZmlsdGVyKElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudDw9NTAsICMgbm8gbmVlZCB0byBjb25zaWRlciBpbnRlcmZlcmVuY2U+PTUwJQogICAgICAgICAgIGNvbXBhcmlzb24gJWluJSBwb3NpdGl2ZV9jb21wYXJpc29ucykgJT4lIAogICAgZmlsdGVyKHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnLCAhYmVsb3dfbm90Y2gpICU+JQogICAgZ2dwbG90KGFlcyhkaWZmLCBjb2xvdXI9YmlubmVkX2ludGVuc2l0eSkpICsKICAgIGdlb21fZGVuc2l0eSgpICsKICAgIHRoZW1lX2NhbXByb3QoYmFzZV9zaXplPTE1KSArCiAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTEsIGhqdXN0PTEpKSArCiAgICBmYWNldF9ncmlkKGJpbm5lZF9pbnRlcmZlcmVuY2V+Y29tcGFyaXNvbiwgc2NhbGVzPSdmcmVlJykgKwogICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1sb2cyKGV4cGVjdGVkKSksCiAgICAgICAgICAgICAgIGRhdGE9ZXhwZWN0ZWRbKGV4cGVjdGVkJHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleHBlY3RlZCRjb21wYXJpc29uICVpbiUgcG9zaXRpdmVfY29tcGFyaXNvbnMpLF0sCiAgICAgICAgICAgICAgIGNvbG91cj1nZXRfY2F0X3BhbGV0dGUoMSksIGxpbmV0eXBlPTIpICsKICAgIHlsYWIoJ0RlbnNpdHknKSArCiAgICB4bGFiKCdEaWZmZXJlbmNlIGluIGludGVuc2l0eScpICsKICAgIHhsaW0oLTYsMykgKwogICAgZ2d0aXRsZSh4KSArCiAgICBzY2FsZV9jb2xvdXJfZGlzY3JldGUobmFtZT0nSW50ZW5zaXR5JykKICAKICBwcmludChwKQogIHByaW50KHAgKyBhZXMoY29sb3VyPWJpbm5lZF9pbnRlcmZlcmVuY2UpICsgZmFjZXRfZ3JpZChiaW5uZWRfYXZlcmFnZV9zbn5jb21wYXJpc29uKSArCiAgICBzY2FsZV9jb2xvdXJfZGlzY3JldGUobmFtZT0nSW50ZXJmZXJlbmNlICglKScpKQogIAogIHByaW50KHAgKyBhZXMoY29sb3VyPWJpbm5lZF9xKSArIHNjYWxlX2NvbG91cl9kaXNjcmV0ZShuYW1lPSdQZXJjb2xhdG9yIFEnKSkKICBwcmludChwICsgYWVzKGNvbG91cj1iaW5uZWRfaW50ZXJmZXJlbmNlKSArIGZhY2V0X2dyaWQoYmlubmVkX3F+Y29tcGFyaXNvbikgKwogICAgICAgICAgc2NhbGVfY29sb3VyX2Rpc2NyZXRlKG5hbWU9J0ludGVyZmVyZW5jZSAoJSknKSkKCiAgCiAgcmV0dXJuKE5VTEwpCn0pCmBgYAoKYGBge3J9CnF1YW50X3ZzX21lYW4gJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICAgIHAgPC0gcXVhbnRfdnNfbWVhbltbeF1dICU+JQogICAgZmlsdGVyKElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudDw9NjApICU+JSAjIG5vIG5lZWQgdG8gY29uc2lkZXIgaW50ZXJmZXJlbmNlPj02MCUKICAgIGZpbHRlcihzcGVjaWVzPT0nUy5jZXJldmlzaWFlJywgIWJlbG93X25vdGNoLCBjb21wYXJpc29uPT0nNiB2cyAxJykgJT4lCiAgICBncm91cF9ieShiaW5uZWRfaW50ZXJmZXJlbmNlLCBiaW5uZWRfaW50ZW5zaXR5KSAlPiUKICAgIHN1bW1hcmlzZShtZWRpYW5fZGlmZj0yXm1lZGlhbihkaWZmLCBuYS5ybT1UUlVFKSwgbj1sZW5ndGgoZGlmZikpICU+JQogICAgZ2dwbG90KGFlcyhiaW5uZWRfaW50ZXJmZXJlbmNlLCBiaW5uZWRfaW50ZW5zaXR5LCBmaWxsPW1lZGlhbl9kaWZmKSkgKwogICAgZ2VvbV90aWxlKGNvbG91cj0nZ3JleScpICsKICAgIHRoZW1lX2NhbXByb3QoYmFzZV9zaXplPTE1KSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGhpZ2g9Z2V0X2NhdF9wYWxldHRlKDIpWzJdLAogICAgICAgICAgICAgICAgICAgICAgICBsb3c9J3doaXRlJywKICAgICAgICAgICAgICAgICAgICAgICAgbGltaXRzPWMoMCwgNiksIG5hbWU9J09ic2VydmVkXG5mb2xkIGNoYW5nZScpICsKICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT00NSwgdmp1c3Q9MSwgaGp1c3Q9MSkpICsKICAgIHhsYWIoJ0Jpbm5lZCBpbnRlcmZlcmVuY2UnKSArCiAgICB5bGFiKCdCaW5uZWQgaW50ZW5zaXR5JykgKwogICAgZ2d0aXRsZSh4KQogICAgCiAgICBwcmludChwICsgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yb3VuZChtZWRpYW5fZGlmZiwgMSkpLCBzaXplPTMpKQogICAgCiAgICBwcmludChwICsKICAgICAgICAgICAgYWVzKGZpbGw9bikgKwogICAgICAgICAgICBzY2FsZV9maWxsX2dyYWRpZW50KGhpZ2g9Z2V0X2NhdF9wYWxldHRlKDMpWzNdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvdz0nd2hpdGUnKSArCiAgICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWw9biksIHNpemU9MykgKQp9KQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQpxdWFudF92c19tZWFuICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgICBxdWFudF92c19tZWFuW1t4XV0gJT4lCiAgICBmaWx0ZXIoSXNvbGF0aW9uLkludGVyZmVyZW5jZS5pbi5QZXJjZW50PD02MCkgJT4lICMgbm8gbmVlZCB0byBjb25zaWRlciBpbnRlcmZlcmVuY2U+PTYwJQogICAgZmlsdGVyKHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnLCAhYmVsb3dfbm90Y2gsIGNvbXBhcmlzb249PSc2IHZzIDEnKSAlPiUKICAgIGdyb3VwX2J5KGJpbm5lZF9xLCBiaW5uZWRfaW50ZXJmZXJlbmNlLCBiaW5uZWRfaW50ZW5zaXR5KSAlPiUKICAgIHN1bW1hcmlzZShtZWRpYW5fZGlmZj0yXm1lZGlhbihkaWZmLCBuYS5ybT1UUlVFKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKGJpbm5lZF9pbnRlcmZlcmVuY2UsIGJpbm5lZF9pbnRlbnNpdHksIGZpbGw9bWVkaWFuX2RpZmYpKSArCiAgICBnZW9tX3RpbGUoY29sb3VyPSdncmV5JykgKwogICAgZmFjZXRfd3JhcCh+YmlubmVkX3EpICsKICAgIHRoZW1lX2NhbXByb3QoYmFzZV9zaXplPTE1KSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50MihoaWdoPWdldF9jYXRfcGFsZXR0ZSgyKVsyXSwKICAgICAgICAgICAgICAgICAgICAgICAgIGxvdz1nZXRfY2F0X3BhbGV0dGUoMilbMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICBtaWQ9J3doaXRlJywgbWlkcG9pbnQ9MCwKICAgICAgICAgICAgICAgICAgICAgICAgbGltaXRzPWMoLTEsIDYpLCBuYW1lPSdPYnNlcnZlZFxuZm9sZCBjaGFuZ2UnKSArCiAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTEsIGhqdXN0PTEpLAogICAgICAgICAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X3JlY3QoZmlsbD0iZ3JleSIpKSArCiAgICB4bGFiKCdCaW5uZWQgaW50ZXJmZXJlbmNlJykgKwogICAgeWxhYignQmlubmVkIEludGVuc2l0eScpICsKICAgIGdndGl0bGUoeCkKfSkKYGBgCgpgYGB7cn0KCnF1YW50X3ZzX21lYW4gJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICBwIDwtIHF1YW50X3ZzX21lYW5bW3hdXSAlPiUKICAgIHNlbGVjdChpZCwgc3BlY2llcywgYmlubmVkX3EsIGJpbm5lZF9hdmVyYWdlX3NuLCBiaW5uZWRfaW50ZXJmZXJlbmNlKSAlPiUKICAgIHVuaXF1ZSgpICU+JQogICAgZ3JvdXBfYnkoc3BlY2llcywgYmlubmVkX3EsIGJpbm5lZF9hdmVyYWdlX3NuLCBiaW5uZWRfaW50ZXJmZXJlbmNlKSAlPiUKICAgIHRhbGx5KCkgJT4lCiAgICBnZ3Bsb3QoYWVzKGJpbm5lZF9pbnRlcmZlcmVuY2UsIG4pKSArCiAgICBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpICsKICAgIGZhY2V0X3dyYXAofnNwZWNpZXMsIHNjYWxlcz0nZnJlZScpICsKICAgIHRoZW1lX2NhbXByb3QoYmFzZV9zaXplPTE1KSArCiAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsIHZqdXN0PTEsIGhqdXN0PTEpKSArCiAgICBnZ3RpdGxlKHgpCiAgCiAgcHJpbnQocCkKICBwcmludChwICsgYWVzKGJpbm5lZF9xKSkKICBwcmludChwICsgYWVzKGJpbm5lZF9hdmVyYWdlX3NuKSArCiAgICAgICAgICB4bGFiKCdTaWduYWwvTm9pc2UnKSkKICAKICByZXR1cm4oTlVMTCkKfSkKYGBgCgoKQmFzZWQgb24gdGhlIGFib3ZlLCBJJ20gZ29pbmcgdG8gdXNlIHRoZSBmb2xsb3dpbmcgdGhyZXNob2xkczoKCi0gSXNvbGF0aW9uIGludGVyZmVyZW5jZSA8PSAxMCUKLSBQZXJjb2xhdG9yIFEgdmFsdWUgPD0gMC4wMDEKCkZvciBub3csIHdlIHdvbid0IGZpbGVyIHVzaW5nIFNOIHNpbmNlIHdlIHdhbnQgdG8gZXhwbG9yZSB0aGUgaW1wYWN0IG9mIHRoZSBub3RjaCBvbiBmb2xkIGNoYW5nZXMgaW4gbW9yZSBkZXRhaWwgZmlyc3QKCkZpcnN0IHRob3VnaCwgbGV0J3MgZmlsdGVyIGJ5IFBlcmNvbGF0b3IgUSB2YWx1ZSBvciBJc29sYXRpb24gaW50ZXJlbmNlIGFsb25lIGFuZCBjaGVjayBob3cgaXNvbGF0aW9uIGludGVyZmVyZW5jZSBhZmZlY3RzIFBTTS1sZXZlbCBmb2xkIGNoYW5nZSBlc3RpbWF0ZXMuCmBgYHtyLCBmaWcuaGVpZ2h0PTksIGZpZy53aWR0aD05fQpxdWFudF92c19tZWFuICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgCiAgdG9fcGxvdF9xX2ZsdCA8LSBxdWFudF92c19tZWFuW1t4XV0gJT4lCiAgICBmaWx0ZXIoUGVyY29sYXRvci5xLlZhbHVlPD0wLjAwNSkgJT4lCiAgICBmaWx0ZXIoc3BlY2llcz09J1MuY2VyZXZpc2lhZScpICU+JQogICAgZmlsdGVyKElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudDw1MCkgJT4lCiAgICBhcnJhbmdlKElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudCkKICAKICB0b19wbG90X2ludGVyZmVyZW5jZV9mbHQgPC0gcXVhbnRfdnNfbWVhbltbeF1dICU+JQogICAgZmlsdGVyKElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudDw9MTApICU+JQogICAgZmlsdGVyKHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnKSAlPiUKICAgIGFycmFuZ2UoUGVyY29sYXRvci5xLlZhbHVlKQogIAogIHAgPC0gdG9fcGxvdF9xX2ZsdCAlPiUKICAgIGdncGxvdChhZXMobG9nMihpbnRlbnNpdHkpLCBkaWZmKSkgKwogICAgZ2VvbV9wb2ludChzaXplPTAuMSwgYWxwaGE9MC4xLCBjb2xvdXI9J2dyZXk4MCcpICsKICAgIHRoZW1lX2NhbXByb3QoYmFzZV9zaXplPTEyKSArCiAgICBmYWNldF93cmFwKH5jb21wYXJpc29uLCBzY2FsZXM9J2ZyZWVfeScpICsKICAgIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQ9bG9nMihleHBlY3RlZCkpLAogICAgICAgICAgICAgICBkYXRhPWV4cGVjdGVkW2V4cGVjdGVkJHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnLF0sCiAgICAgICAgICAgICAgIGNvbG91cj0nYmxhY2snLCBsaW5ldHlwZT0yKSArCiAgICB4bGFiKCdUYWcgaW50ZW5zaXR5IChsb2cyKScpICsKICAgIHlsYWIoJ0RpZmZlcmVuY2UgaW4gaW50ZW5zaXR5IChsb2cyKScpICsKICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoZ2V0X2NhdF9wYWxldHRlKDcpKSwKICAgICAgICAgICAgICAgICAgICAgICAgbmFtZT0nSXNvbGF0aW9uIGludGVyZmVyZW5jZSAoJSknKSArCiAgICBnZ3RpdGxlKHgpCiAgCiAgcHJpbnQocCkKICBwcmludChwICsgZ2VvbV9zbW9vdGgoYWVzKGNvbG91cj1iaW5uZWRfaW50ZXJmZXJlbmNlKSwgc2U9RkFMU0UsIHNpemU9MC41KSkKICBwcmludChwICUrJSB0b19wbG90X2ludGVyZmVyZW5jZV9mbHQgKwogICAgICAgICAgZ2VvbV9zbW9vdGgoYWVzKGNvbG91cj1iaW5uZWRfcSksIHNlPUZBTFNFLCBzaXplPTAuNSkgKwogICAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YyhnZXRfY2F0X3BhbGV0dGUoNykpLAogICAgICAgICAgICAgICAgICAgICAgICBuYW1lPSdQZXJjb2xhdG9yIFEgdmFsdWUnKSkKCiAgcmV0dXJuKE5VTEwpCn0pCmBgYApMZXQncyBwbG90IHRoZSBpbnRlbnNpdHkgdnMgZGlmZmVyZW5jZSBpbiBpbnRlbnNpdHkgZm9yIGFsbCBjb21wYXJpc29ucyBhbmQgZmlsdGVyaW5nIHNjaGVtZXMuCmBgYHtyfQpxdWFudF92c19tZWFuICU+JSBuYW1lcygpICU+JSBsYXBwbHkoZnVuY3Rpb24oeCl7CiAgCiAgICB0bXBfZGF0YSA8LSAgcXVhbnRfdnNfbWVhbltbeF1dICU+JQogICAgICBmaWx0ZXIoc3BlY2llcyE9J21peGVkJywgIWNvbXBhcmlzb24gJWluJSBwb3NpdGl2ZV9jb21wYXJpc29ucykKICAgIAogICAgcCA8LSB0bXBfZGF0YSAlPiUKICAgICAgZ2dwbG90KGFlcyhsb2cyKGludGVuc2l0eSksIGRpZmYpKSArCiAgICAgIGdlb21fcG9pbnQoc2l6ZT0wLjEsIGFscGhhPTAuMDUsIGNvbG91cj0nZ3JleTEwJykgKwogICAgICBnZW9tX2RlbnNpdHlfMmQoc2l6ZT0wLjI1LCBjb2xvdXI9Z2V0X2NhdF9wYWxldHRlKDEpKSArCiAgICAgIHRoZW1lX2NhbXByb3QoYmFzZV9zaXplPTEyKSArCiAgICAgIGZhY2V0X2dyaWQoc3BlY2llc35jb21wYXJpc29uLCBzY2FsZXM9J2ZyZWVfeScpICsKICAgICAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdD1sb2cyKGV4cGVjdGVkKSksCiAgICAgICAgICAgICAgICAgZGF0YT1leHBlY3RlZFshZXhwZWN0ZWQkY29tcGFyaXNvbiAlaW4lIHBvc2l0aXZlX2NvbXBhcmlzb25zLF0sCiAgICAgICAgICAgICAgICAgY29sb3VyPSdibGFjaycsIGxpbmV0eXBlPTIpICsKICAgICAgeGxhYignVGFnIGludGVuc2l0eSAobG9nMiknKSArCiAgICAgIHlsYWIoJ0RpZmZlcmVuY2UgaW4gaW50ZW5zaXR5IChsb2cyKScpICsKICAgICAgZ2d0aXRsZSh4KSArCiAgICAgIGNvb3JkX2NhcnRlc2lhbih5bGltPWMoLTQsNCkpCiAgCiAgICBwcmludChwKQoKICAgIHByaW50KHAgJSslICh0bXBfZGF0YSAlPiUKICAgICAgICAgICAgICAgICAgIGZpbHRlcihQZXJjb2xhdG9yLnEuVmFsdWU8PTAuMDA1KSkpCiAgICBwcmludChwICUrJSAodG1wX2RhdGEgJT4lCiAgICAgICAgICAgICAgICAgICBmaWx0ZXIoSXNvbGF0aW9uLkludGVyZmVyZW5jZS5pbi5QZXJjZW50PD0xMCkpKQogICAgcHJpbnQocCAlKyUgKHRtcF9kYXRhICU+JQogICAgICAgICAgICAgICAgICAgZmlsdGVyKFBlcmNvbGF0b3IucS5WYWx1ZTw9MC4wMDUsIElzb2xhdGlvbi5JbnRlcmZlcmVuY2UuaW4uUGVyY2VudDw9MTApKSkKICAgIHByaW50KHAgJSslICh0bXBfZGF0YSAlPiUKICAgICAgICAgICAgICAgICAgIGZpbHRlcihQZXJjb2xhdG9yLnEuVmFsdWU8PTAuMDA1LCBJc29sYXRpb24uSW50ZXJmZXJlbmNlLmluLlBlcmNlbnQ8PTEwLCBBdmVyYWdlLlJlcG9ydGVyLlNOPjEwKSkpCiAgICByZXR1cm4oTlVMTCkKfSkKYGBgCgoKTm93LCBsZXQncyBmaWx0ZXIgdGhlIFBTTXMgYWdhaW5zdCB0aGVzZSB0aHJlc2hvbGRzLgpgYGB7cn0KcHNtX3Jlc19mbHQgPC0gcHNtX3JlcyAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIG91dCA8LSBmaWx0ZXJfVE1UX1BTTXMoeCwgaW50ZXJfdGhyZXNoPTIwLCBzbl90aHJlc2g9MCkKICAKICBvdXQgPC0gb3V0W2ZEYXRhKG91dCkkUGVyY29sYXRvci5xLlZhbHVlPD0wLjAwMSxdCiAgY2FtcHJvdFI6OjptZXNzYWdlX3BhcnNlKGZEYXRhKG91dCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICdNYXN0ZXIuUHJvdGVpbi5BY2Nlc3Npb25zJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBlcmNvbGF0b3IgUSB2YWx1ZSBmaWx0ZXJpbmciKQogIG91dAp9KQoKcHNtX3Jlc19mbHRfc24gPC0gcHNtX3Jlc19mbHQgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICBvdXQgPC0gZmlsdGVyX1RNVF9QU01zKHgsIGludGVyX3RocmVzaD0yMCwgc25fdGhyZXNoPTEwKQogIG91dAp9KQpgYGAKCmBgYHtyfQoKcHNtX3JlcyAlPiUgbmFtZXMoKSAlPiUgbGFwcGx5KGZ1bmN0aW9uKHgpewogIAogIGRhdGFzZXRzIDwtIGxpc3QoJ3VuZmlsdGVyZWQnPXBzbV9yZXMsICdmaWx0ZXJlZCc9cHNtX3Jlc19mbHQsICdcbmZpbHRlcmVkLCBpbmMgUy9OJz1wc21fcmVzX2ZsdF9zbikKICAKICBmb3IoZGF0YXNldCBpbiBuYW1lcyhkYXRhc2V0cykpewogICAgCiAgICBhbGwgPC0gZGF0YXNldHNbW2RhdGFzZXRdXVtbeF1dCiAgICBocyA8LSBhbGxbZkRhdGEoYWxsKSRzcGVjaWVzPT0nSC5zYXBpZW5zJ10KICAgIHNjIDwtIGFsbFtmRGF0YShhbGwpJHNwZWNpZXM9PSdTLmNlcmV2aXNpYWUnXQogICAgCiAgICBzbGljZXMgPC0gbGlzdCgnQWxsJz1hbGwsICdILnNhcGllbnMnPWhzLCAnUy5jZXJldmlzaWFlJz1zYykKICAgIGZvcihzbGljZSBpbiBuYW1lcyhzbGljZXMpKXsKICAgICAgcCA8LSBzbGljZXNbW3NsaWNlXV0gJT4lIHBsb3RfVE1UX25vdGNoKCkgKwogICAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcyAtICVzJywgeCwgc2xpY2UsIGRhdGFzZXQpKQogICAgICBwcmludChwKQogICAgfQogIH0KICAKICByZXR1cm4oTlVMTCkKICAKfSkKCmBgYAoKUGVyLXRhZyBub3RjaCBwbG90cwpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KCnBzbV9yZXMgJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICAKICBkYXRhc2V0cyA8LSBsaXN0KCd1bmZpbHRlcmVkJz1wc21fcmVzLCAnZmlsdGVyZWQnPXBzbV9yZXNfZmx0LCAnXG5maWx0ZXJlZCwgaW5jIFMvTic9cHNtX3Jlc19mbHRfc24pCiAgCiAgZm9yKGRhdGFzZXQgaW4gbmFtZXMoZGF0YXNldHMpKXsKICAgIAogICAgYWxsIDwtIGRhdGFzZXRzW1tkYXRhc2V0XV1bW3hdXQogICAgaHMgPC0gYWxsW2ZEYXRhKGFsbCkkc3BlY2llcz09J0guc2FwaWVucyddCiAgICBzYyA8LSBhbGxbZkRhdGEoYWxsKSRzcGVjaWVzPT0nUy5jZXJldmlzaWFlJ10KICAgIAogICAgc2xpY2VzIDwtIGxpc3QoJ0FsbCc9YWxsLCAnSC5zYXBpZW5zJz1ocywgJ1MuY2VyZXZpc2lhZSc9c2MpCiAgICBmb3Ioc2xpY2UgaW4gbmFtZXMoc2xpY2VzKSl7CiAgICAgIHAgPC0gc2xpY2VzW1tzbGljZV1dICU+JSBwbG90X1RNVF9ub3RjaChmYWNldF9ieV9zYW1wbGU9VFJVRSkgKwogICAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcyAtICVzJywgeCwgc2xpY2UsIGRhdGFzZXQpKQogICAgICBwcmludChwKQogICAgfQogIH0KICAKICByZXR1cm4oTlVMTCkKICAKfSkKYGBgCgpUYWxsaWVzIGZvciBmcmFjdGlvbiBzdWItbm90Y2ggUFNNcyBwZXIgcHJvdGVpbgpgYGB7cn0KCnBzbV9yZXMgJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICAKICBkYXRhc2V0cyA8LSBsaXN0KCd1bmZpbHRlcmVkJz1wc21fcmVzLCAnZmlsdGVyZWQnPXBzbV9yZXNfZmx0LCAnXG5maWx0ZXJlZCwgaW5jIFMvTic9cHNtX3Jlc19mbHRfc24pCiAgCiAgZm9yKGRhdGFzZXQgaW4gbmFtZXMoZGF0YXNldHMpKXsKICAgIAogICAgYWxsIDwtIGRhdGFzZXRzW1tkYXRhc2V0XV1bW3hdXQogICAgaHMgPC0gYWxsW2ZEYXRhKGFsbCkkc3BlY2llcz09J0guc2FwaWVucyddCiAgICBzYyA8LSBhbGxbZkRhdGEoYWxsKSRzcGVjaWVzPT0nUy5jZXJldmlzaWFlJ10KICAgIAogICAgc2xpY2VzIDwtIGxpc3QoJ0FsbCc9YWxsLCAnSC5zYXBpZW5zJz1ocywgJ1MuY2VyZXZpc2lhZSc9c2MpCiAgICBmb3Ioc2xpY2UgaW4gbmFtZXMoc2xpY2VzKSl7CiAgICAgIAogICAgICBub3RjaF9wZXJfcHJvdGVpbiA8LSBnZXRfbm90Y2hfcGVyX3Byb3RlaW4oc2xpY2VzW1tzbGljZV1dKSAlPiUKICAgICAgICBmaWx0ZXIoZnJhY3Rpb25fYmVsb3c+MCkKICAgICAgCiAgICAgIHAgPC0gcGxvdF9mcmFjdGlvbl9iZWxvd19ub3RjaF9wZXJfcHJvdChub3RjaF9wZXJfcHJvdGVpbikgKwogICAgICAgIGdndGl0bGUoc3ByaW50ZignJXMgLSAlcyAtICVzJywgeCwgc2xpY2UsIGRhdGFzZXQpKQogICAgICAKICAgICAgcHJpbnQocCkKICAgICAgfQogIH0KICAKICByZXR1cm4oTlVMTCkKICAKfSkKCgpgYGAKCgpNaXNzaW5nIHZhbHVlcyBmcmVxdWVuY2llcy4KYGBge3J9CnBzbV9yZXMgJT4lIG5hbWVzKCkgJT4lIGxhcHBseShmdW5jdGlvbih4KXsKICAKICBkYXRhc2V0cyA8LSBsaXN0KCd1bmZpbHRlcmVkJz1wc21fcmVzLCAnZmlsdGVyZWQnPXBzbV9yZXNfZmx0LCAnXG5maWx0ZXJlZCwgaW5jIFMvTic9cHNtX3Jlc19mbHRfc24pCiAgCiAgZm9yKGRhdGFzZXQgaW4gbmFtZXMoZGF0YXNldHMpKXsKICAgIAogICAgYWxsIDwtIGRhdGFzZXRzW1tkYXRhc2V0XV1bW3hdXQogICAgaHMgPC0gYWxsW2ZEYXRhKGFsbCkkc3BlY2llcz09J0guc2FwaWVucyddCiAgICBzYyA8LSBhbGxbZkRhdGEoYWxsKSRzcGVjaWVzPT0nUy5jZXJldmlzaWFlJ10KICAgIAogICAgc2xpY2VzIDwtIGxpc3QoJ0FsbCc9YWxsLCAnSC5zYXBpZW5zJz1ocywgJ1MuY2VyZXZpc2lhZSc9c2MpCiAgICBmb3Ioc2xpY2UgaW4gbmFtZXMoc2xpY2VzKSl7CiAgICAgIHBsb3ROQShzbGljZXNbW3NsaWNlXV0sIHBOQSA9IDApCiAgICB9CiAgfQogIAogIHJldHVybihOVUxMKQogIAp9KQoKYGBgCgpTYXZlIG91dCBvYmplY3RzIGZvciBkb3duc3RyZWFtIG5vdGVib29rcwpgYGB7cn0Kc2F2ZVJEUyhxdWFudF92c19tZWFuLCAnLi4vcmVzdWx0cy9xdWFudF92c19tZWFuLnJkcycpCnNhdmVSRFMocHNtX3Jlc19mbHQsICcuLi9yZXN1bHRzL3BzbV9yZXNfZmx0LnJkcycpCnNhdmVSRFMocHNtX3Jlc19mbHRfc24sICcuLi9yZXN1bHRzL3BzbV9yZXNfZmx0X3NuLnJkcycpCnNhdmVSRFMoZXhwZWN0ZWQsICcuLi9yZXN1bHRzL2V4cGVjdGVkLnJkcycpCmBgYAoKCg==